package com.mengshu.token.interceptor;

import com.mengshu.token.annotation.LoginIgnore;
import com.mengshu.token.api.LoginUser;
import com.mengshu.token.annotation.Permissions;
import com.mengshu.token.api.PermissionLoginUser;
import com.mengshu.token.consts.TokenConsts;
import com.mengshu.token.exceptions.PlatformException;
import com.mengshu.token.exceptions.TokenException;
import com.mengshu.token.exceptions.PermissionException;
import com.mengshu.token.handler.PermissionFailureHandler;
import com.mengshu.token.handler.PermissionFinishedHandler;
import com.mengshu.token.helper.LoginHelper;
import com.mengshu.token.utils.RequestUtils;
import com.mengshu.token.utils.ThreadLocalUtils;
import com.mengshu.token.utils.TokenConfigUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *     登录权限验证拦截
 * </p>
 *
 * @author liuning
 * @date 10:50 下午 2022/1/28
 */
@Slf4j
public class PermissionHandlerInterceptor implements HandlerInterceptor, HandlerMethodArgumentResolver {

    private TokenConfigUtils tokenConfigUtils;

    private LoginHelper loginHelper;

    private PermissionFailureHandler permissionFailureHandler;

    private PermissionFinishedHandler permissionFinishedHandler;

    private final static String THREAD_LOCAL_TOKEN = TokenConsts.THREAD_LOCAL_TOKEN;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    private Map<String, Set<String>> platformExcludes = new HashMap<>();

    public PermissionHandlerInterceptor(TokenConfigUtils tokenConfigUtils, LoginHelper loginHelper, PermissionFailureHandler permissionFailureHandler, PermissionFinishedHandler permissionFinishedHandler) {
        this.tokenConfigUtils = tokenConfigUtils;
        this.loginHelper = loginHelper;
        this.permissionFailureHandler = permissionFailureHandler;
        this.permissionFinishedHandler = permissionFinishedHandler;

        this.tokenConfigUtils.getTokenConfigs().forEach((platform, tokenConfig) -> {
            String excludes = StringUtils.hasText(tokenConfig.getExcludes()) ? tokenConfig.getExcludes() : "";
            this.platformExcludes.put(platform, Arrays.stream(excludes.split(","))
                    .filter(p -> StringUtils.hasText(p)).collect(Collectors.toSet()));
        });

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        if (handler instanceof HandlerMethod ) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            if (BasicErrorController.class.isAssignableFrom(handlerMethod.getBeanType())) {
                return true;
            }

            String platform = RequestUtils.getPlatform(request);
            platform = StringUtils.hasText(platform) ? platform : tokenConfigUtils.getDefaultPlatform();
            if (!platformExcludes.containsKey(platform)) {
                throw new PlatformException("平台不合法");
            }

            String servletPath = request.getServletPath();
            if (platformExcludes.get(platform).stream().anyMatch(pattern -> antPathMatcher.match(pattern, servletPath))) {
                if (log.isDebugEnabled()) log.debug("跳过登录拦截");
                return true;
            }

            // 判断controller上有没有LoginIgnore
            LoginIgnore loginIgnore = handlerMethod.getBeanType().getAnnotation(LoginIgnore.class);
            if (loginIgnore == null) {
                // 判断方法上有没有LoginIgnore
                loginIgnore = handlerMethod.getMethod().getAnnotation(LoginIgnore.class);
            }
            if (null != loginIgnore) {
                String[] first = loginIgnore.platform(), second = loginIgnore.value();
                String[] result = Arrays.copyOf(first, first.length + second.length);
                System.arraycopy(second, 0, result, first.length, second.length);

                List<String> loginIgnorePlatforms = Arrays.asList(result);
                Set<String> platforms = loginIgnorePlatforms.stream().filter(p -> StringUtils.hasText(p)).collect(Collectors.toSet());
                if (platforms.size() < 1) {
                    platforms.add(tokenConfigUtils.getDefaultPlatform());
                }
                final String pt = platform;
                if (platforms.stream().anyMatch(p -> pt.equals(p))) {
                    if (log.isDebugEnabled()) log.debug("跳过登录拦截");
                    return true;
                }
            }

            String tokenString = RequestUtils.getToken(request);
            if (!StringUtils.hasText(tokenString)) {
                if (permissionFailureHandler != null) {
                    return permissionFailureHandler.failureToken(request, response, handlerMethod);
                }
                throw new TokenException("请求头中不包含token信息");
            }


            final LoginUser loginUser = loginHelper.getLoginUser(tokenString);
            if (loginUser == null) {
                if (permissionFailureHandler != null) {
                    return permissionFailureHandler.failureToken(request, response, handlerMethod);
                }
                throw new TokenException("token不存在或已过期");
            }

            // token存放在ThreadLocal中
            ThreadLocalUtils.set(THREAD_LOCAL_TOKEN, tokenString);

            // 权限验证
            final Permissions permissions = handlerMethod.getMethod().getAnnotation(Permissions.class);
            if (permissions != null) {
                if (loginUser instanceof PermissionLoginUser) {
                    PermissionLoginUser permissionLoginUser = (PermissionLoginUser) loginUser;
                    final List<String> defPermissions = Arrays.asList(permissions.value());
                    final Optional<Set<String>> userPermissions = Optional.ofNullable(permissionLoginUser.getPermissions());

                    if (!defPermissions.stream().allMatch(defPermission ->
                            userPermissions.filter(ups -> ups.contains(defPermission)).isPresent())) {
                        if (permissionFailureHandler != null) {
                            return permissionFailureHandler.failurePermission(request, response, handlerMethod);
                        }
                        throw new PermissionException("权限校验未通过");
                    }
                } else {
                    if (permissionFailureHandler != null) {
                        return permissionFailureHandler.failurePermission(request, response, handlerMethod);
                    }
                    throw new PermissionException("当前用户非权限用户");
                }
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
        if (permissionFinishedHandler != null) {
            permissionFinishedHandler.finishedPermission(request, response, handler, ex);
        }
        // 清除ThreadLocal中的资源
        ThreadLocalUtils.clear(THREAD_LOCAL_TOKEN);
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return LoginUser.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request, WebDataBinderFactory binderFactory) {
        final String tokenString = RequestUtils.getToken(request);
        if (StringUtils.hasText(tokenString)) {
            return loginHelper.getLoginUser(tokenString);
        }
        return null;
    }

}
